import { SandBox, SandBoxType } from '../../interfaces';
import { getTargetValue } from '../common';

function isPropConfigurable(target, prop) {
  const descriptor = Object.getOwnPropertyDescriptor(target, prop);
  return descriptor ? descriptor.configurable : true;
}

function setWindowProp(prop, value, toDelete) {
  if (value === undefined && toDelete) {
    delete window[prop];
  } else if (isPropConfigurable(window, prop) && typeof prop !== 'symbol') {
    Object.defineProperty(window, prop, {
      writable: true,
      configurable: true
    });
    window[prop] = value;
  }
}

export default class SingularProxySandbox {
  addedPropsMapInSandbox = new Map();
  modifiedPropsOriginalValueMapInSandbox = new Map();
  currentUpdatedPropsValueMap = new Map();
  sandboxRunning = true;

  active() {
    if (!this.sandboxRunning) {
      this.currentUpdatedPropsValueMap.forEach((v, p) => setWindowProp(p, v));
    }

    this.sandboxRunning = true;
  }

  inactive() {
    if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] ${this.name} modified global properties restore...`, [...this.addedPropsMapInSandbox.keys(), ...this.modifiedPropsOriginalValueMapInSandbox.keys()]);
    }

    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => setWindowProp(p, v));
    this.addedPropsMapInSandbox.forEach((_, p) => setWindowProp(p, undefined, true));
    this.sandboxRunning = false;
  }

  constructor(name) {
    this.name = name;
    this.type = SandBoxType.LegacyProxy;
    const {
      addedPropsMapInSandbox,
      modifiedPropsOriginalValueMapInSandbox,
      currentUpdatedPropsValueMap
    } = this;
    const self = this;
    const rawWindow = window;
    const fakeWindow = Object.create(null);
    const proxy = new Proxy(fakeWindow, {
      set(_, p, value) {
        if (self.sandboxRunning) {
          if (!rawWindow.hasOwnProperty(p)) {
            addedPropsMapInSandbox.set(p, value);
          } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
            const originalValue = rawWindow[p];
            modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
          }

          currentUpdatedPropsValueMap.set(p, value);
          rawWindow[p] = value;
          return true;
        }

        if (process.env.NODE_ENV === 'development') {
          console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}!`);
        }

        return true;
      },

      get(_, p) {
        if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {
          return proxy;
        }

        const value = rawWindow[p];
        return getTargetValue(rawWindow, value);
      },

      has(_, p) {
        return p in rawWindow;
      }

    });
    this.proxy = proxy;
  }

}